// SPDX-FileCopyrightText: 2023 CASTest Corporation Limited
// SPDX-License-Identifier: LGPL-v3

#ifndef ICTEST_LOGICDRIVENTABLE_H
#define ICTEST_LOGICDRIVENTABLE_H

#include "atpg/ATPGDefine.h"
#include "netlist/PrimNetlist.h"

namespace ictest {

// 逻辑驱动表
class LogicDrivenTable {
public:
  LogicDrivenTable(PrimNetlist *prim_netlist) {
    preGenerateAllCubes(prim_netlist);
  }

  ~LogicDrivenTable() {}
  static bool compare_res(std::vector<std::pair<Gate *, int>> &A,
                          std::vector<std::pair<Gate *, int>> &B) {
    int num_in = A.size();
    for (int fi = 0; fi < num_in; fi++) {
      if (deter_val(B[fi].second)) {
        if (deter_val(A[fi].second) && A[fi].second != B[fi].second) {
          return false;
        }
      } else if (unknown_val(B[fi].second)) {
        if (deter_val(A[fi].second)) {
          return false;
        }
      }
    }
    return true;
  }

  inline std::vector<std::vector<std::pair<Gate *, int>>>
  possible_cubes(Gate *gate, int target_val, std::vector<int> &curr_vals,
                 std::map<Gate *, int> &gate2scanchainid,
                 std::vector<int> &is_falling_edge_dff,
                 std::vector<bool> &no_assign) {
    std::vector<std::vector<std::pair<Gate *, int>>> res;

    auto test_cubes = get_test_cubes(gate, target_val);
    for (auto &tb : test_cubes) {
      if (gate->GetGType() != GType::G_DFF) {
        assert(tb.size() == gate->FaninSize());
      }

      bool conflict = false;
      for (int fi = 0; fi < gate->FaninSize(); fi++) {
        int tb_fi = tb[fi];
        if (gate->GetGType() == GType::G_DFF && fi == 2 && deter_val(tb_fi)) {
          tb_fi = is_falling_edge_dff[gate->GetGId()] ? 1 - tb_fi : tb_fi;
        }

        if (deter_val(tb_fi) && deter_val(curr_vals[fi]) &&
            tb_fi != curr_vals[fi]) {
          conflict = true;
          break;
        }

        auto fanin_gate = gate->FaninGates()[fi];
        if (no_assign[fanin_gate->GetGId()] && deter_val(tb_fi)) {
          conflict = true;
          break;
        }
      }

      if (gate->GetGType() == GType::G_DFF) {
        if (deter_val(tb[4]) && deter_val(curr_vals[4]) &&
            tb[4] != curr_vals[4]) {
          conflict = true;
        }
      }
      if (!conflict) {
        res.emplace_back();
        for (int fi = 0; fi < gate->FaninSize(); fi++) {
          // assign possible val 0, decrease unused imply in the future
          int tb_fi = tb[fi];
          if (gate->GetGType() == GType::G_DFF && fi == 2 && deter_val(tb_fi)) {
            tb_fi = is_falling_edge_dff[gate->GetGId()] ? 1 - tb_fi : tb_fi;
          }
          if (tb_fi == curr_vals[fi]) {
            res.back().emplace_back(gate->FaninGates()[fi], LOGIC_x);
          } else {
            res.back().emplace_back(gate->FaninGates()[fi],
                                    static_cast<int>(tb_fi));
          }
        }
        if (gate->GetGType() == GType::G_DFF) {
          if (tb[4] == curr_vals[4]) {
            res.back().emplace_back(gate->FaninGates()[4], LOGIC_x);
          } else {
            res.back().emplace_back(gate->FaninGates()[4],
                                    static_cast<int>(tb[4]));
          }
        }
      }
    }

    std::sort(res.begin(), res.end(), compare_res);

    // eliminate same possible justify assignment
    std::vector<int> del(res.size(), false);
    for (int i = 0; i < res.size(); i++) {
      if (del[i])
        continue;
      for (int j = i + 1; j < res.size(); j++) {
        if (del[j])
          continue;
        bool all_same = true;
        bool i_is_deter = false;
        bool j_is_deter = false;
        if (gate->GetGType() == GType::G_DFF && gate2scanchainid.count(gate)) {
          for (int fi = 0; fi <= gate->FaninSize(); fi++) {
            if (deter_val(res[i][fi].second) && deter_val(res[j][fi].second) &&
                res[i][fi].second != res[j][fi].second) {
              all_same = false;
              break;
            }
            if (!i_is_deter && deter_val(res[i][fi].second) &&
                unknown_val(res[j][fi].second)) {
              i_is_deter = true;
              if (j_is_deter) {
                all_same = false;
                break;
              }
            }
            if (!j_is_deter && deter_val(res[j][fi].second) &&
                unknown_val(res[i][fi].second)) {
              j_is_deter = true;
              if (i_is_deter) {
                all_same = false;
                break;
              }
            }
          }
        } else {
          for (int fi = 0; fi < gate->FaninSize(); fi++) {
            if (deter_val(res[i][fi].second) && deter_val(res[j][fi].second) &&
                res[i][fi].second != res[j][fi].second) {
              all_same = false;
              break;
            }
            if (!i_is_deter && deter_val(res[i][fi].second) &&
                unknown_val(res[j][fi].second)) {
              i_is_deter = true;
              if (j_is_deter) {
                all_same = false;
                break;
              }
            }
            if (!j_is_deter && deter_val(res[j][fi].second) &&
                unknown_val(res[i][fi].second)) {
              j_is_deter = true;
              if (i_is_deter) {
                all_same = false;
                break;
              }
            }
          }
        }

        if (all_same) {
          assert(!i_is_deter);
          del[j] = true;
        }
      }
    }

    std::vector<std::vector<std::pair<Gate *, int>>> cubes;
    for (int i = 0; i < res.size(); i++) {
      if (!del[i]) {
        cubes.emplace_back(res[i]);
      }
    }
    if (cubes.size() == 1) {
      bool all_x = true;
      for (auto fanin : cubes[0]) {
        if (deter_val(fanin.second)) {
          all_x = false;
          break;
        }
      }
      if (all_x) {
        //        assert(false);
        cubes.clear();
      }
    }

    return cubes;
  }

  inline std::vector<std::vector<int>> &get_test_cubes(Gate *gate, int val) {
    auto logic_type = gate->GetGType();
    auto num_in = gate->FaninSize();
    //        assert(_new_logic_map[hash_cube(logic_type, num_in)] !=-1);
    return _test_cubes[_new_logic_map[hash_cube(static_cast<int>(logic_type),
                                                num_in)]][val];
  }

  inline std::vector<std::vector<int>> &get_new_test_cubes(Gate *gate,
                                                           int val) {
    auto logic_type = gate->GetGType();
    auto num_in = gate->FaninSize();
    //        assert(_new_logic_map[hash_cube(logic_type, num_in)] !=-1);
    return _new_test_cubes[_new_logic_map[hash_cube(
        static_cast<int>(logic_type), num_in)]][val];
  }

  static inline int hash_cube(int logic_type, int num_in) {
    int type = logic_type;
    if (logic_type >= static_cast<int>(GType::G_XOR) &&
        logic_type <= static_cast<int>(GType::G_XNOR)) {
      type = logic_type - static_cast<int>(GType::G_XOR) + 15;
    } else if (logic_type >= static_cast<int>(GType::G_ABUF) &&
               logic_type <= static_cast<int>(GType::G_TIEZ)) {
      type = logic_type - static_cast<int>(GType::G_ABUF) + 17;
    } else if (logic_type >= static_cast<int>(GType::G_WIRE) &&
               logic_type <= static_cast<int>(GType::G_DLAT)) {
      type = logic_type - static_cast<int>(GType::G_WIRE) + 20;
    }
    return (type << 3) + num_in;
  }

private:
  void preGenerateAllCubes(PrimNetlist *prim_netlist) {
    int num_test_cubes = 0;
    _eval_array.resize(prim_netlist->NumGates(), LOGIC_x);
    // alloc _logic_map size
    int total_logic_type_count = 28;
    _new_logic_map.resize(8 * (total_logic_type_count + 1));
    _new_logic_map.assign(_new_logic_map.size(), -1);

    for (int n = 0; n < prim_netlist->NumGates(); n++) {
      auto gate = prim_netlist->GetPrimNetlist()[n];
      auto logic_type = gate->GetGType();
      auto num_in = gate->FaninSize();
      std::vector<int> tb;
      std::vector<int> new_tb;
      std::vector<std::vector<std::vector<int>>> *test_cubes;
      std::vector<std::vector<std::vector<int>>> *new_test_cubes;
      assert(num_in <= 8);

      // No need to handle fanout branch or primary input.
      if (logic_type == GType::G_PI || logic_type == GType::G_TIE0 ||
          logic_type == GType::G_TIE1 || logic_type == GType::G_TIEX)
        continue;

      //            if ( _logic_map.find(hash_cube(logic_type, num_in)) ==
      //            _logic_map.end() ) {
      //                _test_cubes.emplace_back();
      //                _logic_map[hash_cube(logic_type, num_in)] =
      //                num_test_cubes ++; test_cubes = &_test_cubes.back();
      //                test_cubes->resize(2);
      //            }
      if (_new_logic_map[hash_cube(static_cast<int>(logic_type), num_in)] ==
          -1) {
        _test_cubes.emplace_back();
        _new_test_cubes.emplace_back();
        _new_logic_map[hash_cube(static_cast<int>(logic_type), num_in)] =
            num_test_cubes++;
        test_cubes = &_test_cubes.back();
        new_test_cubes = &_new_test_cubes.back();
        test_cubes->resize(2);
        new_test_cubes->resize(2);
      } else {
        continue;
      }

      switch (logic_type) {
      case GType::G_XOR:
      case GType::G_XNOR: {
        for (int i = 0; i < std::pow(num_in, 2); i++) {
          int seq = i;
          for (int j = 0; j < num_in; j++) {
            int bit = seq & 1;
            seq = seq >> 1;
            tb.emplace_back(bit);

            new_tb.emplace_back(j);
            new_tb.emplace_back(bit);

            _eval_array[gate->FaninGates()[j]->GetGId()] = bit;
          }
          // Evaluate gate.
          int val = eval(gate);
          (*test_cubes)[val].emplace_back(tb);
          (*new_test_cubes)[val].emplace_back(new_tb);
          tb.clear();
          new_tb.clear();
        }
      } break;
      case GType::G_OR:
      case GType::G_NOR:
      case GType::G_AND:
      case GType::G_NAND: {
        for (int i = 0; i < num_in; i++) {
          for (int j = 0; j < num_in; j++) {
            if (j == i) {
              tb.emplace_back(static_cast<int>(gate->GetCtrInVal()));
              new_tb.emplace_back(j);
              new_tb.emplace_back(static_cast<int>(gate->GetCtrInVal()));
            } else {
              tb.emplace_back(LOGIC_x);
            }
          }
          (*test_cubes)[static_cast<int>(gate->GetCtrOutVal())].emplace_back(
              tb);
          (*new_test_cubes)[static_cast<int>(gate->GetCtrOutVal())]
              .emplace_back(new_tb);
          tb.clear();
          new_tb.clear();
        }

        for (int j = 0; j < num_in; j++) {
          tb.emplace_back(static_cast<int>(gate->GetNonCtrInVal()));

          new_tb.emplace_back(j);
          new_tb.emplace_back(static_cast<int>(gate->GetNonCtrInVal()));
        }
        (*test_cubes)[static_cast<int>(gate->GetNonCtrOutVal())].emplace_back(
            tb);
        (*new_test_cubes)[static_cast<int>(gate->GetNonCtrOutVal())]
            .emplace_back(new_tb);

      } break;
      case GType::G_BUF:
      case GType::G_ABUF:
      case GType::G_PO:
      case GType::G_BRH: {
        tb = {LOGIC_0};
        new_tb = {0, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);

        tb.clear();
        new_tb.clear();
        tb = {LOGIC_1};
        new_tb = {0, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);

      } break;
      case GType::G_NOT: {
        tb = {LOGIC_1};
        new_tb = {0, LOGIC_1};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);

        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0};
        new_tb = {0, LOGIC_0};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
      } break;
      case GType::G_MUX: {
        //   sel       in0      in1
        tb = {LOGIC_0, LOGIC_0, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_1, LOGIC_x, LOGIC_0};
        new_tb = {0, LOGIC_1, 2, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_x, LOGIC_0, LOGIC_0};
        new_tb = {1, LOGIC_0, 2, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        // logic 1
        tb = {LOGIC_0, LOGIC_1, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_1, LOGIC_x, LOGIC_1};
        new_tb = {0, LOGIC_1, 2, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_x, LOGIC_1, LOGIC_1};
        new_tb = {1, LOGIC_1, 2, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

      } break;
      case GType::G_DLAT: {
        // logic 0
        // set       reset    clock    data_in
        tb = {LOGIC_0, LOGIC_1, LOGIC_0, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_1, 2, LOGIC_x};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_1, LOGIC_0};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_1, 3, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        // logic 1
        tb = {LOGIC_1, LOGIC_0, LOGIC_0, LOGIC_x};
        new_tb = {0, LOGIC_1, 1, LOGIC_0, 2, LOGIC_0};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_1, LOGIC_1};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_1, 3, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();
      } break;
      case GType::G_DFF: {
        tb = {LOGIC_0, LOGIC_1, LOGIC_0, LOGIC_x, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_1, 2, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_1, LOGIC_0, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_1, 3, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_0, LOGIC_x, LOGIC_0};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_0, 4, LOGIC_0};
        (*test_cubes)[LOGIC_0].emplace_back(tb);
        (*new_test_cubes)[LOGIC_0].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        // logic 1
        tb = {LOGIC_1, LOGIC_0, LOGIC_0, LOGIC_x, LOGIC_x};
        new_tb = {0, LOGIC_1, 1, LOGIC_0, 2, LOGIC_0};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_1, LOGIC_1, LOGIC_x};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_1, 3, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();

        tb = {LOGIC_0, LOGIC_0, LOGIC_0, LOGIC_x, LOGIC_1};
        new_tb = {0, LOGIC_0, 1, LOGIC_0, 2, LOGIC_0, 4, LOGIC_1};
        (*test_cubes)[LOGIC_1].emplace_back(tb);
        (*new_test_cubes)[LOGIC_1].emplace_back(new_tb);
        tb.clear();
        new_tb.clear();
      } break;
      default:
        std::cout << static_cast<int>(logic_type) << std::endl;
        assert(false);
      }
    }
  }

  inline int eval(Gate *gate) {
    int value;
    int size = gate->FaninSize();
    auto logic_type = gate->GetGType();
    assert(logic_type == GType::G_XOR || logic_type == GType::G_XNOR);
    switch (gate->GetGType()) {
    case GType::G_XOR:
      value = _eval_array[gate->FaninGates()[0]->GetGId()];
      for (int i = 1; i < size; i++) {
        value =
            xor_table_five[value][_eval_array[gate->FaninGates()[i]->GetGId()]];
      }
      break;
    case GType::G_XNOR:
      value = _eval_array[gate->FaninGates()[0]->GetGId()];
      for (int i = 1; i < size; i++) {
        value =
            xor_table_five[value][_eval_array[gate->FaninGates()[i]->GetGId()]];
      }
      value = not_table_five[value];
      break;
    default:
      assert(false);
    }

    return value;
  }

public:
  // [LOGIC_TYPE][N_INPUTS_TEST_CUBE][TEST_CUBE]
  std::vector<std::vector<std::vector<std::vector<int>>>> _test_cubes;
  std::vector<std::vector<std::vector<std::vector<int>>>> _new_test_cubes;
  std::unordered_map<int, int> _logic_map;
  std::vector<int> _new_logic_map;
  std::vector<int> _eval_array;
};

} // namespace ictest

#endif // ICTEST_LOGICDRIVENTABLE_H
